From: Seth Michael Larson Date: Fri, 23 Jan 2026 14:59:35 +0000 (-0600) Subject: gh-144125: email: verify headers are sound in BytesGenerator X-Git-Tag: archive/raspbian/3.9.2-1+rpi1+deb11u6^2^2~1 X-Git-Url: https://dgit.raspbian.org/%22http:/www.example.com//%22mailto:tmurad%40gmail.com/%22/%22http:/www.example.com/%22mailto:tmurad%40gmail.com/%22?a=commitdiff_plain;h=213c7563a1b81e567a4c455f95d840b8c106d48b;p=python3.9.git gh-144125: email: verify headers are sound in BytesGenerator Co-authored-by: Denis Ledoux Co-authored-by: Denis Ledoux <5822488+beledouxdenis@users.noreply.github.com> Co-authored-by: Petr Viktorin <302922+encukou@users.noreply.github.com> Co-authored-by: Bas Bloemsaat <1586868+basbloemsaat@users.noreply.github.com> Origin: backport, https://github.com/python/cpython/commit/052e55e7d44718fe46cbba0ca995cb8fcc359413 Gbp-Pq: Name CVE-2026-1299.patch --- diff --git a/Lib/email/generator.py b/Lib/email/generator.py index 89224ae..98cc4a0 100644 --- a/Lib/email/generator.py +++ b/Lib/email/generator.py @@ -22,6 +22,7 @@ NL = '\n' # XXX: no longer used by the code below. NLCRE = re.compile(r'\r\n|\r|\n') fcre = re.compile(r'^From ', re.MULTILINE) NEWLINE_WITHOUT_FWSP = re.compile(r'\r\n[^ \t]|\r[^ \n\t]|\n[^ \t]') +NEWLINE_WITHOUT_FWSP_BYTES = re.compile(br'\r\n[^ \t]|\r[^ \n\t]|\n[^ \t]') @@ -430,7 +431,16 @@ class BytesGenerator(Generator): # This is almost the same as the string version, except for handling # strings with 8bit bytes. for h, v in msg.raw_items(): - self._fp.write(self.policy.fold_binary(h, v)) + folded = self.policy.fold_binary(h, v) + if self.policy.verify_generated_headers: + linesep = self.policy.linesep.encode() + if not folded.endswith(linesep): + raise HeaderWriteError( + f'folded header does not end with {linesep!r}: {folded!r}') + if NEWLINE_WITHOUT_FWSP_BYTES.search(folded.removesuffix(linesep)): + raise HeaderWriteError( + f'folded header contains newline: {folded!r}') + self._fp.write(folded) # A blank line always separates headers from body self.write(self._NL) diff --git a/Lib/test/test_email/test_generator.py b/Lib/test/test_email/test_generator.py index d29400f..a641f87 100644 --- a/Lib/test/test_email/test_generator.py +++ b/Lib/test/test_email/test_generator.py @@ -264,7 +264,7 @@ class TestGenerator(TestGeneratorBase, TestEmailBase): typ = str def test_verify_generated_headers(self): - """gh-121650: by default the generator prevents header injection""" + # gh-121650: by default the generator prevents header injection class LiteralHeader(str): name = 'Header' def fold(self, **kwargs): @@ -285,6 +285,8 @@ class TestGenerator(TestGeneratorBase, TestEmailBase): with self.assertRaises(email.errors.HeaderWriteError): message.as_string() + with self.assertRaises(email.errors.HeaderWriteError): + message.as_bytes() class TestBytesGenerator(TestGeneratorBase, TestEmailBase): diff --git a/Lib/test/test_email/test_policy.py b/Lib/test/test_email/test_policy.py index ff1ddf7..d4a5eb3 100644 --- a/Lib/test/test_email/test_policy.py +++ b/Lib/test/test_email/test_policy.py @@ -279,7 +279,7 @@ class PolicyAPITests(unittest.TestCase): policy.fold("Subject", subject) def test_verify_generated_headers(self): - """Turning protection off allows header injection""" + # Turning protection off allows header injection policy = email.policy.default.clone(verify_generated_headers=False) for text in ( 'Header: Value\r\nBad: Injection\r\n', @@ -302,6 +302,10 @@ class PolicyAPITests(unittest.TestCase): message.as_string(), f"{text}\nBody", ) + self.assertEqual( + message.as_bytes(), + f"{text}\nBody".encode(), + ) # XXX: Need subclassing tests. # For adding subclassed objects, make sure the usual rules apply (subclass diff --git a/Misc/NEWS.d/next/Security/2026-01-21-12-34-05.gh-issue-144125.TAz5uo.rst b/Misc/NEWS.d/next/Security/2026-01-21-12-34-05.gh-issue-144125.TAz5uo.rst new file mode 100644 index 0000000..e6333e7 --- /dev/null +++ b/Misc/NEWS.d/next/Security/2026-01-21-12-34-05.gh-issue-144125.TAz5uo.rst @@ -0,0 +1,4 @@ +:mod:`~email.generator.BytesGenerator` will now refuse to serialize (write) headers +that are unsafely folded or delimited; see +:attr:`~email.policy.Policy.verify_generated_headers`. (Contributed by Bas +Bloemsaat and Petr Viktorin in :gh:`121650`).